<?php
namespace lib\builtin\auth;

use lib\builtin\auth\AuthConfigInvalidException;

class Auth
{
    /**
     * @var array
     */
    protected $permissions;

    /**
     * @var array
     */
    protected $idToPermission;

    /**
     * @var array
     */
    protected $codeToPermission;

    /**
     * @var array
     */
    protected $roles;

     /**
     * @var array
     */
    protected $idToRole;

    /**
     * @var
     */
    protected $nameToRole;

    /**
     * 创建Auth实例
     * @param array $config 权限配置数组
     */
    public function __construct($config)
    {
        if (!(isset($config['permissions']) && isset($config['roles']))) {
            throw new AuthConfigInvalidException('根键不存在');
        }

        $this->parsePermissions($config['permissions']);
        $this->parseRoles($config['roles']);
    }

    /**
     * 注册单例绑定
     */
    public static function register()
    {
        bind(self::class, function () {
            $config = config('auth.');
            return new self($config);
        });
    }

    /**
     * 根据给定的角色或角色组判断是否具有对应权限
     * @param int|string|array $roleOrRoles 角色（ID或名称）或角色组
     * @param int|string $perm 权限（ID或名称）
     * @return bool
     * @throws NoPermissionException
     */
    public function can($roleOrRoles, $perm)
    {
        $perm = $this->findPermission($perm);
        if ($perm === null) {
            throw new NoPermissionException('找不到权限', false);
        }
        $id = $perm['id'];
        if (!is_array($roleOrRoles)) {
            $roleOrRoles = [$roleOrRoles];
        }
        foreach ($roleOrRoles as $item) {
            if (is_numeric($item) && \intval($item) === 0) {
                continue;
            }
            $role = $this->findRole($item);
            if ($role === null) {
                throw new NoPermissionException('找不到角色', false);
            }
            if (in_array($id, $role['permissions'], true)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 查找某权限
     * @param int|string $idOrCode 权限（ID或名称）
     * @return array|null
     */
    public function findPermission($idOrCode)
    {
        if (is_numeric($idOrCode)) {
            if (isset($this->idToPermission[$idOrCode])) {
                return $this->idToPermission[$idOrCode];
            } else {
                return null;
            }
        } else {
            if (isset($this->codeToPermission[$idOrCode])) {
                return $this->codeToPermission[$idOrCode];
            } else {
                return null;
            }
        }
    }

    /**
     * 查找某角色
     * @param int|string $idOrRoleName 角色（ID或名称）
     * @return array
     */
    public function findRole($idOrRoleName)
    {
        if (is_numeric($idOrRoleName)) {
            if (isset($this->idToRole[$idOrRoleName])) {
                return $this->idToRole[$idOrRoleName];
            } else {
                return null;
            }
        } else {
            if (isset($this->nameToRole[$idOrRoleName])) {
                return $this->nameToRole[$idOrRoleName];
            } else {
                return null;
            }
        }
    }

    /**
     * 获取所有的权限
     *
     * @return array
     */
    public function getAllPermissions()
    {
        return $this->permissions;
    }

    /**
     * 获取所有的角色
     *
     * @return array
     */
    public function getAllRoles()
    {
        return $this->roles;
    }

    /**
     * 整理permissions
     *
     * @param array $permissions
     * @throws \lib\builtin\auth\AuthConfigInvalidException
     */
    protected function parsePermissions($permissions)
    {
        $result = [];
        $idMap = [];
        $codeMap = [];

        foreach ($permissions as $i => $item) {
            if (isset($item['id'])) {
                if (isset($item['code'])) {
                    $id = $item['id'];
                    $code = $item['code'];
                    $text = isset($item['text']) ? $item['text'] : $item['code'];
                } else {
                    throw new AuthConfigInvalidException('权限ID' . $item['id'] . '格式错误');
                }
            } else {
                if (isset($item[0]) && isset($item[1])) {
                    $id = $item[0];
                    $code = $item[1];
                    if (isset($item[2])) {
                        $text = $item[2];
                    }
                } else {
                    throw new AuthConfigInvalidException('权限' . $i . '格式错误');
                }
            }
            $result[] = [
                'id' => $id,
                'code' => $code,
                'text' => $text,
            ];
        }

        foreach ($result as $perm) {
            if (isset($idMap[$perm['id']])) {
                throw new AuthConfigInvalidException('权限ID' . $perm['id'] . '重复');
            }
            $idMap[$perm['id']] = $perm;
        }

        foreach ($result as $perm) {
            if (isset($codeMap[$perm['code']])) {
                throw new AuthConfigInvalidException('权限' . $perm['code'] . '重复');
            }
            $codeMap[$perm['code']] = $perm;
        }

        $this->permissions = $result;
        $this->idToPermission = $idMap;
        $this->codeToPermission = $codeMap;
    }

    /**
     * 整理roles
     *
     * @param array $roles
     * @throws \lib\builtin\auth\AuthConfigInvalidException
     * @return array
     */
    protected function parseRoles($roles)
    {
        $result = [];
        $idMap = [];
        $nameMap = [];

        foreach ($roles as $i => $item) {
            if (isset($item['id'])) {
                if (isset($item['role_name']) && isset($item['permissions'])) {
                    $id = $item['id'];
                    $roleName = $item['role_name'];
                    $permissions = $item['permissions'];
                    $text = isset($item['text']) ? $item['text'] : $item['role_name'];
                } else {
                    throw new AuthConfigInvalidException('角色ID' . $item['id'] . '格式错误');
                }
            } else {
                if (isset($item[0]) && isset($item[1]) && isset($item[2])) {
                    if (isset($item[3])) {
                        $id = $item[0];
                        $roleName = $item[1];
                        $permissions = $item[2];
                        $text = $item[1];
                    } else {
                        $id = $item[0];
                        $roleName = $item[1];
                        $text = $item[2];
                        $permissions = $item[3];
                    }
                } else {
                    throw new AuthConfigInvalidException('角色' . $i . '格式错误');
                }
            }

            $itemPermissions = [];
            foreach ($permissions as $perm) {
                if (is_numeric($perm)) {
                    if (!isset($this->idToPermission[$perm])) {
                        throw new AuthConfigInvalidException('权限ID' . $perm . '不存在');
                    } else {
                        $wholePerm = $this->idToPermission[$perm];
                    }
                } else {
                    if (!isset($this->codeToPermission[$perm])) {
                        throw new AuthConfigInvalidException('权限' . $perm . '不存在');
                    } else {
                        $wholePerm = $this->codeToPermission[$perm];
                    }
                }
                $itemPermissions[] = $wholePerm['id'];
            }

            $result[] = [
                'id' => $id,
                'role_name' => $roleName,
                'text' => $text,
                'permissions' => $itemPermissions,
            ];
        }

        foreach ($result as $role) {
            if (isset($idMap[$role['id']])) {
                throw new AuthConfigInvalidException('角色ID' . $role['id'] . '重复');
            }
            $idMap[$role['id']] = $role;
        }

        foreach ($result as $role) {
            if (isset($nameMap[$role['role_name']])) {
                throw new AuthConfigInvalidException('角色' . $role['role_name'] . '重复');
            }
            $nameMap[$role['role_name']] = $role;
        }

        $this->roles = $result;
        $this->idToRole = $idMap;
        $this->nameToRole = $nameMap;
    }
}
